/*
 * Filename : Combatant.java
 * Description : **COMPLETE**
 * History  : Date               Engineer       Description
 *            ---------          ------------   --------------------------------
 *            August 21, 2007    waitek         Initial creation.
 */

package com.lct.eq2.data;

//import com.lct.eq2.watchers.TraumaticSwipeWatcher;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;

public class Combatant implements Comparator, Serializable {
    static final long serialVersionUID = 910113719800965776L;
    
    private ArrayList attacks;
    private HashMap incomingAttackHash;
    private HashMap outgoingAttackHash;
    private HashMap selfAttackHash;

    private boolean hasTraumaticSwipe;
    private String lastSwiper;
    
    private Duration duration;
    private Duration encounterDuration;
    private double dps = 0;
    private int totalDamageDealt = 0;
    private int totalDamageTaken = 0;
    private int totalHealingDone = 0;
    private String name;
    private String career;
    
    private int outgoingMeleeMaxHit = 0;
    private int outgoingMeleeMinHit = 99999999;
    private int outgoingNonMeleeMaxHit = 0;
    private int outgoingNonMeleeMinHit = 99999999;
    private int outgoingUnknownMaxHit = 0;
    private int outgoingUnknownMinHit = 99999999;
    
    private int incomingMeleeMaxHit = 0;
    private int incomingMeleeMinHit = 99999999;
    private int incomingNonMeleeMaxHit = 0;
    private int incomingNonMeleeMinHit = 99999999;
    private int incomingUnknownMaxHit = 0;
    private int incomingUnknownMinHit = 99999999;
    
    private int meleeDamageDealt = 0;
    private int nonMeleeDamageDealt = 0;
    private int unknownDamageDealt = 0;
    private int meleeDamageTaken = 0;
    private int nonMeleeDamageTaken = 0;
    private int unknownDamageTaken = 0;
    
    private int totalIncomingMeleeHits = 0;
    private int totalIncomingNonMeleeHits = 0;
    private int totalIncomingUnknownHits = 0;
    private int totalOutgoingMeleeHits = 0;
    private int totalOutgoingNonMeleeHits = 0;
    private int totalOutgoingUnknownHits = 0;
    
    private int totalIncomingMeleeSwings = 0;
    private int totalIncomingNonMeleeSwings = 0;
    private int totalIncomingUnknownSwings = 0;
    private int totalOutgoingMeleeSwings = 0;
    private int totalOutgoingNonMeleeSwings = 0;
    private int totalOutgoingUnknownSwings = 0;

    private int totalIncomingMeleeCrits = 0;
    private int totalIncomingNonMeleeCrits = 0;
    private int totalIncomingUnknownCrits = 0;
    private int totalOutgoingMeleeCrits = 0;
    private int totalOutgoingNonMeleeCrits = 0;
    private int totalOutgoingUnknownCrits = 0;

    private String avoidedAtLeastOne = null;

    //private long id;
    private int meleeSwings = 0;
    
    /** Creates a new instance of Combatant */
    public Combatant() {
        //this.id = System.currentTimeMillis();
        this.outgoingAttackHash = new HashMap();
        this.incomingAttackHash = new HashMap();
        this.selfAttackHash = new HashMap();
        this.attacks = new ArrayList();
        this.duration = new Duration();
    }
    
    public Combatant(Attack attack, boolean zonewide) {
        //this.id = System.currentTimeMillis();
        this.outgoingAttackHash = new HashMap();
        this.incomingAttackHash = new HashMap();
        this.selfAttackHash = new HashMap();
        this.name = attack.getAttacker();
        this.attacks = new ArrayList();
        this.duration = new Duration();
        
        if (attack.getAttackDirectionInt() == Attack.ATTACK_TYPE_OUTGOING) {
            this.duration.setDurationStartAsDate(new Date(attack.getTimeStamp()));
        }
        this.addAttack(attack, zonewide);
    }
    
    public void setTraumaticSwiped(boolean swiped, String caster) {
        if (!swiped) {
            if (this.lastSwiper.equalsIgnoreCase(caster)) {
                this.hasTraumaticSwipe = swiped;
            }
        } else if (swiped) {
            this.hasTraumaticSwipe = swiped;
            this.lastSwiper = caster;
        }
    }

    public boolean hasTraumaticSwipe() {
//        return this.hasTraumaticSwipe;
        return false;
    }

    public String getCareer() {
        return career;
    }

    public void setCareer(String career) {
        this.career = career;
    }
    
    public void addMeleeSwing() {
        this.meleeSwings++;
    }
    
    @Override
    public String toString() {
        return this.name + (this.career != null && !this.career.equalsIgnoreCase("") ? " (" + this.career + ")": "");
    }
    
    public int getOutgoingUnknownMinHit() {
        return outgoingUnknownMinHit;
    }
    
    public int getOutgoingUnknownMaxHit() {
        return outgoingUnknownMaxHit;
    }
    
    public int getOutgoingNonMeleeMinHit() {
        return outgoingNonMeleeMinHit;
    }

    public int getOutgoingNonMeleeMaxHit() {
        return outgoingNonMeleeMaxHit;
    }

    public int getOutgoingMeleeMinHit() {
        return outgoingMeleeMinHit;
    }

    public int getOutgoingMeleeMaxHit() {
        return outgoingMeleeMaxHit;
    }
    
    public int getMeleeDamageDealt() {
        return this.meleeDamageDealt;
    }
    
    public int getNonMeleeDamageDealt() {
        return this.nonMeleeDamageDealt;
    }
    
    public int getUnknownDamageDealt() {
        return this.unknownDamageDealt;
    }
    
    public int getMeleeDamageTaken() {
        return this.meleeDamageTaken;
    }
    
    public int getNonMeleeDamageTaken() {
        return this.nonMeleeDamageTaken;
    }
    
    public int getUnknownDamageTaken() {
        return this.nonMeleeDamageTaken;
    }
    
    public Duration getDuration() {
        return this.duration;
    }
    
    public int getTotalDamageDealt() {
        return this.totalDamageDealt;
    }
    
    public int getTotalHealingDone() {
        return this.totalHealingDone;
    }
    
    public int getDamageTaken() {
        return this.totalDamageTaken;
    }
    
    public ArrayList getAttacks() {
        return this.attacks;
    }
    
    public void setTotalDamageDealtForZonewide(int totalDmg, Duration zonewide) {
        this.totalDamageDealt = totalDmg;
        this.encounterDuration = zonewide;
    }
    
    public void setTotalDamageDealt(int totalDmg) {
        this.totalDamageDealt = totalDmg;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getName() {
        String rval = "Unknown";
        if (this.name != null) {
            rval = this.name;
        }
        return rval;
    }
    
    public double getDPS() {
        return this.dps;
    }
    
    public double getExtDPS(Duration duration) {
        double rval = 0;
        double seconds = duration.getDurationInSeconds();
        if (seconds > 0) {
            rval = (this.totalDamageDealt / seconds);
        }
        //System.out.println("Combatant " + this.getName() + " is doing " + rval + " Extended DPS.");
        return rval;
    }
    
    private void addToHashMap(Attack attack) {
        if (attack.isTrueSelf() || (attack.getAttackDirectionIntRelativeToCombatant(this) == Attack.ATTACK_TYPE_OUTGOING)) {
//            System.out.println("Added outgoing attack relative to " + this.getName() + ", target: " + attack.getTarget() + 
//                    ", attacker: " + attack.getAttacker() + ", type: " + attack.getAttackType());
            if (this.outgoingAttackHash.containsKey(attack.toString())) {
                AttackHashMap map = (AttackHashMap)this.outgoingAttackHash.get(attack.toString());
                if (attack.getAttackTypeInt() != Attack.ATTACK_TYPE_UNKNOWN && map.getAttackTypeInt() == Attack.ATTACK_TYPE_UNKNOWN) {
                    map.setAttackTypeInt(attack.getAttackTypeInt());
                    if (attack.getAttackClassInt() != map.getAttackClassInt()) {
                        map.setAttackClassInt(attack.getAttackClassInt());
                    }
                }
                ArrayList list = (ArrayList)map.get(attack.toString());
                if (!list.contains(attack)) {
                    list.add(attack);
                }
                map.put(attack.toString(), list);
                this.outgoingAttackHash.put(map.toString(), map);
            } else {
                AttackHashMap map = new AttackHashMap();
                ArrayList list = new ArrayList();
                map.setAttackClassInt(attack.getAttackClassInt());
                map.setDirection(Attack.ATTACK_TYPE_OUTGOING);
                map.setMainAttackString(attack.toString());
                map.setAttackTypeInt(attack.getAttackTypeInt());
                if (!list.contains(attack)) {
                    list.add(attack);
                }
                map.put(attack.toString(), list);
                this.outgoingAttackHash.put(map.toString(), map);
            }
        } else if (attack.getAttackDirectionIntRelativeToCombatant(this) == Attack.ATTACK_TYPE_INCOMING) {
//            System.out.println("Added incoming attack relative to " + this.getName() + ", target: " + attack.getTarget() + 
//                    ", attacker: " + attack.getAttacker() + ", type: " + attack.getAttackType());
            if (this.incomingAttackHash.containsKey(attack.toString())) {
                AttackHashMap map = (AttackHashMap)this.incomingAttackHash.get(attack.toString());
//                if (attack.getTarget().equalsIgnoreCase("darkwindmaker") &&
//                        attack.getAmountOfAttackDamage() > 0 && attack.getAttackName().equalsIgnoreCase("brutal inquiry")) {
//                    boolean stop = true;
//                    System.out.println("Map attackTypeInt: " + map.getAttackTypeInt() + ", " +
//                            "attack attackClassInt: " + attack.getAttackClassInt() + ", attack attackTypeInt" + attack.getAttackTypeInt());
//                }

                if (attack.getAttackTypeInt() != Attack.ATTACK_TYPE_UNKNOWN && map.getAttackTypeInt() == Attack.ATTACK_TYPE_UNKNOWN) {
                    map.setAttackTypeInt(attack.getAttackTypeInt());
                    if (attack.getAttackClassInt() != map.getAttackClassInt()) {
                        map.setAttackClassInt(attack.getAttackClassInt());
                    }
                }
                ArrayList list = (ArrayList)map.get(attack.toString());
                if (!list.contains(attack)) {
                    list.add(attack);
                }
                map.put(attack.toString(), list);
                this.incomingAttackHash.put(map.toString(), map);
            } else {
                AttackHashMap map = new AttackHashMap();
                ArrayList list = new ArrayList();
                map.setAttackClassInt(attack.getAttackClassInt());
                map.setDirection(Attack.ATTACK_TYPE_INCOMING);
                map.setMainAttackString(attack.toString());
                map.setAttackTypeInt(attack.getAttackTypeInt());
                if (!list.contains(attack)) {
                    list.add(attack);
                }
                map.put(attack.toString(), list);
                this.incomingAttackHash.put(attack.toString(), map);
            }
        } else { //if (attack.getAttackDirectionIntRelativeToCombatant(this.getName()) == Attack.ATTACK_TYPE_SELF) {
//            System.out.println("Added self attack relative to " + this.getName() + ", target: " + attack.getTarget() +
//                    ", attacker: " + attack.getAttacker() + ", type: " + attack.getAttackType());
            if (this.selfAttackHash.containsKey(attack.toString())) {
                AttackHashMap map = (AttackHashMap)this.selfAttackHash.get(attack.toString());
                ArrayList list = (ArrayList)map.get(attack.toString());
                if (!list.contains(attack)) {
                    list.add(attack);
                }
                map.put(attack.toString(), list);
                this.selfAttackHash.put(map.toString(), map);
            } else {
                AttackHashMap map = new AttackHashMap();
                ArrayList list = new ArrayList();
                map.setAttackClassInt(attack.getAttackClassInt());
                map.setDirection(Attack.ATTACK_TYPE_SELF);
                map.setMainAttackString(attack.toString());
                if (!list.contains(attack)) {
                    list.add(attack);
                }
                map.put(attack.toString(), list);
                this.selfAttackHash.put(attack.toString(), map);
            }
        }
    }
    
    public ArrayList getIncomingAttackHashMaps() {
        ArrayList rval = new ArrayList();
        Iterator iter = this.incomingAttackHash.keySet().iterator();
        while (iter.hasNext()) {
            rval.add(this.incomingAttackHash.get(iter.next()));
        }
        return rval;
    }
    
    public ArrayList getOutgoingAttackHashMaps() {
        ArrayList rval = new ArrayList();
        Iterator iter = this.outgoingAttackHash.keySet().iterator();
        while (iter.hasNext()) {
            rval.add(this.outgoingAttackHash.get(iter.next()));
        }
        return rval;
    }
    
    public ArrayList getSelfAttackHashMaps() {
        ArrayList rval = new ArrayList();
        try {
            Iterator iter = this.selfAttackHash.keySet().iterator();
            while (iter.hasNext()) {
                rval.add(this.selfAttackHash.get(iter.next()));
            }
        } catch (NullPointerException ex) {
            // nothing
        }
        return rval;
    }
    
    public void addAttack(Attack attack, boolean zonewide) {
        if (!this.attacks.contains(attack)) {
            int amount = attack.getAmountOfAttackDamage();
            this.addToHashMap(attack);

            String name = this.getName();
            int direction = attack.getAttackDirectionIntRelativeToCombatant(this);

            if (attack.isHeal()) {
                if (direction == Attack.ATTACK_TYPE_OUTGOING || direction == Attack.ATTACK_TYPE_SELF)  {
                    this.totalHealingDone += amount;
                }
                
                if (this.duration.getStartDate() == null) {
                    this.duration.setDurationStartAsDate(new Date(attack.getTimeStamp()));
                } else {
                    this.duration.setDurationEndAsDate(new Date(attack.getTimeStamp()));
                }
            } else {
                if (direction == Attack.ATTACK_TYPE_OUTGOING) {
                    if (attack.isMelee()) {
                        if (attack.isCrit()) {
                            this.totalOutgoingMeleeCrits++;
                        }

                        if (!attack.isAvoided()) {
                            this.totalOutgoingMeleeHits++;
                            if (amount < this.outgoingMeleeMinHit) {
                                this.outgoingMeleeMinHit = amount;
                            }
                            if (amount > this.outgoingMeleeMaxHit) {
                                this.outgoingMeleeMaxHit = amount;
                            }
                        }

                        this.totalOutgoingMeleeSwings++;
                        this.meleeDamageDealt += amount;
                    } else if (attack.isNonMelee()) {
                        if (attack.isCrit()) {
                            this.totalOutgoingNonMeleeCrits++;
                        }

                        if (!attack.isAvoided()) {
                            this.totalOutgoingNonMeleeHits++;
                            if (amount < this.outgoingNonMeleeMinHit) {
                                this.outgoingNonMeleeMinHit = amount;
                            }
                            if (amount > this.outgoingNonMeleeMaxHit) {
                                this.outgoingNonMeleeMaxHit = amount;
                            }
                        }
                        this.totalOutgoingNonMeleeSwings++;
                        this.nonMeleeDamageDealt += amount;
                    } else if (attack.isUnknown()) {
                        if (attack.isCrit()) {
                            this.totalOutgoingUnknownCrits++;
                        }

                        if (!attack.isAvoided()) {
                            this.totalOutgoingUnknownHits++;
                            if (amount < this.outgoingUnknownMinHit) {
                                this.outgoingUnknownMinHit = amount;
                            }
                            if (amount > this.outgoingUnknownMaxHit) {
                                this.outgoingUnknownMaxHit = amount;
                            }
                        }
                        this.totalOutgoingUnknownSwings++;
                        this.unknownDamageDealt += amount;
                    }

                    this.totalDamageDealt += amount;
                    if (this.duration.getStartDate() == null) {
                        this.duration.setDurationStartAsDate(new Date(attack.getTimeStamp()));
                    } else {
                        this.duration.setDurationEndAsDate(new Date(attack.getTimeStamp()));
                    }
                } else {
                    if (attack.isAvoided()) {
                        this.avoidedAtLeastOne = "true";
                    }
                    if (attack.isMelee()) {
                        if (attack.isCrit()) {
                            this.totalIncomingMeleeCrits++;
                        }

                        if (!attack.isAvoided()) {
                            this.totalIncomingMeleeHits++;
                            if (amount < this.incomingMeleeMinHit) {
                                this.incomingMeleeMinHit = amount;
                            }
                            if (amount > this.incomingMeleeMaxHit) {
                                this.incomingMeleeMaxHit = amount;
                            }
                        }

                        this.totalIncomingMeleeSwings++;
                        this.meleeDamageTaken += amount;
                    } else if (attack.isNonMelee()) {
                        if (attack.isCrit()) {
                            this.totalIncomingNonMeleeCrits++;
                        }

                        if (!attack.isAvoided()) {
                            this.totalIncomingNonMeleeHits++;
                            if (amount < this.incomingNonMeleeMinHit) {
                                this.incomingNonMeleeMinHit = amount;
                            }
                            if (amount > this.incomingNonMeleeMaxHit) {
                                this.incomingNonMeleeMaxHit = amount;
                            }
                        }
                        this.totalIncomingNonMeleeSwings++;
                        this.nonMeleeDamageTaken += amount;
                    } else if (attack.isUnknown()) {
                        if (attack.isCrit()) {
                            this.totalIncomingUnknownCrits++;
                        }

                        if (!attack.isAvoided()) {
                            this.totalIncomingUnknownHits++;
                            if (amount < this.incomingUnknownMinHit) {
                                this.incomingUnknownMinHit = amount;
                            }
                            if (amount > this.incomingUnknownMaxHit) {
                                this.incomingUnknownMaxHit = amount;
                            }
                        }
                        this.totalIncomingUnknownSwings++;
                        this.unknownDamageTaken += amount;
                    }
                    this.totalDamageTaken += amount;
                }

                // right now, incoming damage does NOT start the combatant's dps timer

                if (this.duration.getStartDate() != null) {
                    this.duration.setDurationEndAsDate(new Date(attack.getTimeStamp()));
                }
                double seconds = this.duration.getDurationInSeconds();
                if (seconds > 0) {
                    this.dps = this.totalDamageDealt / seconds;
                }
            }
            
            //System.out.println(this.duration.getDuration());
            this.attacks.add(attack);
        }
    }

    public String getAvoidedAtLeastOne() {
        return this.avoidedAtLeastOne;
    }

    public void setAvoidedAtLeastOne(String bool) {
        this.avoidedAtLeastOne = bool;
    }

    public boolean avoidedAtLeastOne() {
        return Boolean.parseBoolean(avoidedAtLeastOne);
    }
    
    public void debugAttack(Attack attack) {
        System.out.println("Combatant::debugAttack() - " + attack.getAttacker() + " dealt " 
                        + attack.getAmountOfAttackDamage() + " " + attack.getAttackDirectionRelativeToCombatant(this) + " damage to " + attack.getTarget()
                        + " with " + attack.getAttackName() + ", " + attack.getAttackType() 
                        + " " + attack.getAttackClass() + " damage.");
    }
    
    public void setEncounterDuration(Duration duration) {
        this.encounterDuration = duration;
    }
    
    public double getExtendedDPS() {
        double rval = 0;
        double seconds = this.encounterDuration.getDurationInSeconds();
        if (seconds > 0) {
            rval = this.totalDamageDealt / seconds;
        }
        return rval;
    }

    public int compare(Object o1, Object o2) {
        int rval = 0;
        if (((Combatant)o1).getExtendedDPS() > ((Combatant)o2).getExtendedDPS()) {
            rval = -1;
        } else {
            rval = 1;
        }
        return rval;
    }
    
    public int getIncomingNonMeleeMaxHit() {
        return incomingNonMeleeMaxHit;
    }

    public int getIncomingNonMeleeMinHit() {
        return incomingNonMeleeMinHit;
    }
    
    public int getIncomingMeleeMaxHit() {
        return incomingMeleeMaxHit;
    }

    public int getIncomingMeleeMinHit() {
        return incomingMeleeMinHit;
    }

    public int getIncomingUnknownMinHit() {
        return incomingUnknownMinHit;
    }
    
    public int getIncomingUnknownMaxHit() {
        return incomingUnknownMaxHit;
    }
    
    public int getTotalIncomingMeleeCrits() {
        return totalIncomingMeleeCrits;
    }

    public int getTotalIncomingMeleeHits() {
        return totalIncomingMeleeHits;
    }

    public int getTotalIncomingMeleeSwings() {
        return totalIncomingMeleeSwings;
    }

    public int getTotalIncomingNonMeleeCrits() {
        return totalIncomingNonMeleeCrits;
    }

    public int getTotalIncomingNonMeleeSwings() {
        return totalIncomingNonMeleeSwings;
    }

    public int getTotalIncomingNonMeleeHits() {
        return totalIncomingNonMeleeHits;
    }

    public int getTotalIncomingUnknownCrits() {
        return totalIncomingUnknownCrits;
    }

    public int getTotalIncomingUnknownHits() {
        return totalIncomingUnknownHits;
    }

    public int getTotalIncomingUnknownSwings() {
        return totalIncomingUnknownSwings;
    }
    
    public int getTotalOutgoingMeleeCrits() {
        return totalOutgoingMeleeCrits;
    }

    public int getTotalOutgoingMeleeHits() {
        return totalOutgoingMeleeHits;
    }

    public int getTotalOutgoingMeleeSwings() {
        return totalOutgoingMeleeSwings;
    }

    public int getTotalOutgoingNonMeleeCrits() {
        return totalOutgoingNonMeleeCrits;
    }

    public int getTotalOutgoingNonMeleeHits() {
        return totalOutgoingNonMeleeHits;
    }

    public int getTotalOutgoingNonMeleeSwings() {
        return totalOutgoingNonMeleeSwings;
    }
    
    public int getTotalOutgoingUnknownCrits() {
        return totalOutgoingUnknownCrits;
    }

    public int getTotalOutgoingUnknownHits() {
        return totalOutgoingUnknownHits;
    }

    public int getTotalOutgoingUnknownSwings() {
        return totalOutgoingUnknownSwings;
    }

    public int getMeleeSwings() {
        return meleeSwings;
    }
}
